package hydrap2p.library.listening;

import hydrap2p.ServiceLocation;
import hydrap2p.library.Database;
import hydrap2p.library.LibraryListener;
import hydrap2p.library.LibrarySubscriptionType;
import hydrap2p.library.Range;
import hydrap2p.library.SharedItemInfo;

import java.sql.SQLException;
import java.util.LinkedList;
import java.util.List;

import org.apache.thrift.TException;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.transport.TSocket;
import org.apache.thrift.transport.TTransport;
import org.apache.thrift.transport.TTransportException;

public class ListenerManager{

	private volatile static ListenerManager instance;
	private List<Listener> listeners;

	private ListenerManager()
	{
		listeners=new LinkedList<Listener>();
	}
	public static ListenerManager getInstance()
	{
		if (instance==null){
			synchronized(ListenerManager.class)
			{
				if (instance==null)
					instance=new ListenerManager();
			}
		}
		return instance;
	}
	
	public boolean subscribe(ServiceLocation location, LibrarySubscriptionType type)
	{

		TTransport transport = new TSocket(location.address,location.port);
		try {
			transport.open();
		} catch (TTransportException e) {
			// Failed to connect to this listener, don't add
			return false;
		}
		Listener newlistener=new Listener();
		// TODO make this connection inside the newlistener		
		newlistener.connection=new LibraryListener.Client(new TBinaryProtocol(transport));
		newlistener.location=location;
		newlistener.subscription=type;
		try {
			if (newlistener.connection.supportedInterfaces().contains("LibraryListener"))
			{
				synchronized(listeners)
				{
					if (listeners.contains(newlistener))
					{
						listeners.remove(newlistener);
						listeners.add(newlistener);
					}
					else
					{
						listeners.add(newlistener);
						subscribeNew(newlistener);
					}
					
				}
			}
		} catch (TException e) {
			// Failed to talk to this listener, don't add
			return false;
		}
		return true;
	}
	
	private void subscribeNew(Listener listener)
	{
		try {
			Database db=Database.getInstance();
			new ListenerSyncerator(db.getAll(),listener).start();
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}
	
	public void unsubscribe(ServiceLocation location)
	{
		Listener newlistener=new Listener();
		newlistener.location=location;
		synchronized(listeners)
		{
			listeners.remove(newlistener);
		}
	}
	
	public void executeAll(ListenerEvent event)
	{
		List<Listener> mylisteners;
		List<Listener> failed = new LinkedList<Listener>();
		
		synchronized(listeners){
			mylisteners=new LinkedList<Listener>(listeners);
		}
		
		for (Listener listener:mylisteners)
		{
			try {
				listener.execute(event);
			} catch (TException e) {
				failed.add(listener);
			}
		}

		synchronized(listeners){
			listeners.removeAll(failed);
		}
	}
	
	public void changedExtra(SharedItemInfo arg0) {
		executeAll(new ListenerEvent(ListenerEventType.ChangedExtra, arg0));
	}

	public void changedMetadata(SharedItemInfo arg0) {
		executeAll(new ListenerEvent(ListenerEventType.ChangedMetadata, arg0));
	}

	public void deletePartialProgress(SharedItemInfo arg0, Range arg1) {
		executeAll(new ListenerEvent(ListenerEventType.DeletePartialProgress, arg0, arg1));
	}

	public void deleteVerifiedProgress(SharedItemInfo arg0, Range arg1) {
		executeAll(new ListenerEvent(ListenerEventType.DeleteVerifiedProgress, arg0, arg1));
	}

	public void newPartialProgress(SharedItemInfo arg0, Range arg1) {
		executeAll(new ListenerEvent(ListenerEventType.NewPartialProgress, arg0, arg1));
	}

	public void newSharedItem(SharedItemInfo arg0) {
		executeAll(new ListenerEvent(ListenerEventType.NewSharedItem, arg0));
	}
	
	public void newVerifiedProgress(SharedItemInfo arg0, Range arg1) {
		executeAll(new ListenerEvent(ListenerEventType.NewVerifiedProgress, arg0, arg1));
	}
	public void removedSharedItem(SharedItemInfo arg0) {
		executeAll(new ListenerEvent(ListenerEventType.RemovedSharedItem, arg0));
	}
	public void ping() {
		executeAll(new ListenerEvent(ListenerEventType.Ping));
	}
}
